"""解决系统的到岸成本会在销售单开发票之前就进行成本结转的问题"""


import json
import re
from datetime import datetime, date

from odoo import models, api, fields, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError
from odoo.addons import decimal_precision as dp


DATE_FORMAT = '%Y-%m-%d %H:%M:%S'


class DateEncoder(json.JSONEncoder):
    def default(self, obj):
        """date_time转化方法"""
        if isinstance(obj, (datetime, date)):
            return obj.strftime(DATE_FORMAT)
        else:
            return super().default(obj)


class DateDecoder(json.JSONDecoder):
    def default(self, obj):
        # if re.match('^\d{4}-\d{2}-\d{2}$', obj):
        #     return datetime.datetime.strptime(obj, '%Y-%m-%d')
        if re.match('^\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}$', obj):
            return datetime.strptime(obj, DATE_FORMAT)
        else:
            return super().default(obj)

class StockMove(models.Model):
    _inherit = "stock.move"

    out_stock_move_info = fields.Text('出库单信息')

    def _record_out_stock_move_info(self, qty_taken_on_candidate, value_taken_on_candidate, candidate_vals,
                                    in_move):
        """
        在入库单上记录出库单信息{出库单ID：[从入库单上扣减数量，从入库单上扣减价值]}，将信息写入candidate_vals
        :param qty_taken_on_candidate: 扣减数量
        :param value_taken_on_candidate:    扣减价值
        :param candidate_vals:   dict，待更新的参数字典
        :param in_move:   stock.move，出库单信息将被记录到这个入库单上
        :return:
        """
        self.ensure_one()
        in_move.ensure_one()
        info = {}
        out_stock_move_info = in_move.out_stock_move_info
        if out_stock_move_info:
            info = json.loads(out_stock_move_info)

        result = info.setdefault(str(self.id), [0.0, 0.0])
        result[0] += qty_taken_on_candidate
        result[1] += value_taken_on_candidate
        out_stock_move_info = json.dumps(info)
        candidate_vals['out_stock_move_info'] = out_stock_move_info
        return candidate_vals

    def _record_in_stock_move_info(self, out_move, in_move, qty_taken_on_in_move, value_taken_on_in_move):
        """
        在出库单上记录入库单的信息 {入库单ID：[从入库单上削减的数量, 入库时间, 从入库单上削减的价值]}
        :param out_move: stock.move出库单
        :param in_move:  stock.move入库单
        :param qty_taken_on_in_move: float在入库单上削减的数量
        :param value_taken_on_in_move: float在入库单上削减的价值
        :return:
        """
        out_move.ensure_one()
        in_move.ensure_one()
        info = {}
        out_stock_move_info = out_move.out_stock_move_info
        if out_stock_move_info:
            info = json.loads(out_stock_move_info, cls=DateDecoder)
        in_move_id_str = str(in_move.id)
        info[in_move_id_str] = [qty_taken_on_in_move, in_move.date, value_taken_on_in_move]
        out_stock_move_info = json.dumps(info, cls=DateEncoder)
        out_move.out_stock_move_info = out_stock_move_info

    @api.model
    def _get_uninvoiced_out_move(self, in_move):
        """
        获取入库stock.move对应的、还未开票的、已确认的出库stock.move，
        以及出库stock.move在此入库stock.move上对应的数量和价值
        :param in_move:
        :return: {出库ID：[出库单在此入库单上减去的数量（未开票部分），出库单在此入库单上减去的价值（未开票部分）]}
        """
        in_move.ensure_one()
        if in_move._is_in():
            uninvoiced_info = {}
            if in_move.out_stock_move_info:
                out_info = json.loads(in_move.out_stock_move_info)  #  {出库单ID：[从入库单上扣减数量，从入库单上扣减价值]}
                out_stock_ids = self.env['stock.move'].browse([int(i) for i in out_info.keys()])
                # 遍历此入库单对应的全部出库单
                for out_stock_id in out_stock_ids:
                    total_invoiced_qty = out_stock_id.sale_line_id.qty_invoiced
                    if out_stock_id.state == 'done' and out_stock_id.out_stock_move_info:
                        in_info = json.loads(out_stock_id.out_stock_move_info,
                                             cls=DateDecoder)  # {入库单ID：[从入库单上削减的数量, 入库时间,  从入库单上削减的价值]}
                        ordered_ids = sorted(in_info, key=lambda x: (in_info[x][1], x))  # 按FIFO规则排序
                        # 遍历出库单对应的入库单，以获此此入库单对应的开票数量
                        for in_move_id in ordered_ids:
                            this_invoiced_qty = max(min(total_invoiced_qty, in_info[in_move_id][0]), 0)
                            if int(in_move_id) == in_move.id:
                                uninvoiced_qty = uninvoiced_info.setdefault(out_stock_id, [0, 0])
                                unqty = in_info[in_move_id][0] - this_invoiced_qty
                                unvalue = in_info[in_move_id][2] * (unqty / in_info[in_move_id][0])
                                uninvoiced_qty[0] += unqty
                                uninvoiced_qty[1] += unvalue
                                break
                            total_invoiced_qty -= this_invoiced_qty
            return uninvoiced_info
        else:
            raise UserError('只能获取入库stock.move对应的未开票出库信息')

    @api.multi
    def show_it(self):
        end = self._get_uninvoiced_out_move(self)
        raise UserError(str(end))

    # def _get_invoiced_qty(self):
    #     """获取此（入库）move对应的出库move（如果有）上的已开票数量"""
    #     self.ensure_one()
    #     if self._is_in():
    #         invoiced_qty = 0
    #         if self.out_stock_move_info:
    #             out_info = json.loads(self.out_stock_move_info)  # {入库单ID：[从入库单上扣减数量，从入库单上扣减价值]}
    #             out_stock_ids = self.env['stock.move'].browse([int(i) for i in out_info.keys()])
    #             # 遍历此入库单对应的全部出库单
    #             for out_stock_id in out_stock_ids:
    #                 total_invoiced_qty = out_stock_id.sale_line_id.qty_invoiced
    #                 if out_stock_id.state == 'done' and total_invoiced_qty > 0 and out_stock_id.out_stock_move_info:
    #                     in_info = json.loads(out_stock_id.out_stock_move_info,
    #                                          cls=DateDecoder)  # {入库单ID：[从入库单上削减的数量, 入库时间]}
    #                     ordered_ids = sorted(in_info, key=lambda x: (in_info[x][1], x))  # 按FIFO规则排序
    #                     # 遍历出库单对应的入库单，以获此此入库单对应的开票数量
    #                     for out_move_id in ordered_ids:
    #                         this_invoiced_qty = max(min(total_invoiced_qty, in_info[out_move_id][0]), 0)
    #                         if int(out_move_id) == self.id:
    #                             invoiced_qty += this_invoiced_qty
    #                             break
    #                         total_invoiced_qty -= this_invoiced_qty
    #         return invoiced_qty
    #     else:
    #         raise UserError('只能查询入库stock.move对应的出库stock.move的已开票数量！')

    @api.model
    def _run_fifo(self, move, quantity=None):
        """ 重写系统的此方法，目的是在入库和出库的stock.move之间建立FIFO关系，
        便于在成本分摊时，根据入库stock.move找到对应的出库stock.move（如果有），
        进而将分摊的成本不仅加到入库stock.move上，也要加到对应的出库stock.move
        上，后续的出库凭证就可以考虑到分摊的这部分增加的成本了。
        :param quantity: quantity to value instead of `move.product_qty`
        :returns: valued amount in absolute
        """
        move.ensure_one()
        valued_move_lines = move.move_line_ids.filtered(lambda
                                                            ml: ml.location_id._should_be_valued() and not ml.location_dest_id._should_be_valued() and not ml.owner_id)
        valued_quantity = 0
        for valued_move_line in valued_move_lines:
            valued_quantity += valued_move_line.product_uom_id._compute_quantity(valued_move_line.qty_done,
                                                                                 move.product_id.uom_id)
        qty_to_take_on_candidates = quantity or valued_quantity
        candidates = move.product_id._get_fifo_candidates_in_move_with_company(move.company_id.id)
        new_standard_price = 0
        tmp_qty = 0
        tmp_value = 0  # to accumulate the value taken on the candidates
        for candidate in candidates:
            new_standard_price = candidate.price_unit
            if candidate.remaining_qty <= qty_to_take_on_candidates:
                qty_taken_on_candidate = candidate.remaining_qty
            else:
                qty_taken_on_candidate = qty_to_take_on_candidates

            candidate_price_unit = candidate.remaining_value / candidate.remaining_qty
            value_taken_on_candidate = qty_taken_on_candidate * candidate_price_unit

            candidate_vals = {
                'remaining_qty': candidate.remaining_qty - qty_taken_on_candidate,
                'remaining_value': candidate.remaining_value - value_taken_on_candidate,
            }
            # ------ 新增部分2020-8-11 lili ------ #
            candidate_vals = move._record_out_stock_move_info(qty_taken_on_candidate, value_taken_on_candidate,
                                                              candidate_vals, candidate)
            self._record_in_stock_move_info(move, candidate, qty_taken_on_candidate, value_taken_on_candidate)
            # ----------------------------------- #
            candidate.write(candidate_vals)

            qty_to_take_on_candidates -= qty_taken_on_candidate
            tmp_qty += qty_taken_on_candidate
            tmp_value += value_taken_on_candidate
            if qty_to_take_on_candidates == 0:
                break

        if new_standard_price and move.product_id.cost_method == 'fifo':
            move.product_id.sudo().with_context(
                force_company=move.company_id.id).standard_price = new_standard_price
        if qty_to_take_on_candidates == 0:
            if not move.value:
                price_unit = -tmp_value / (move.product_qty or quantity)
            else:
                price_unit = (-(tmp_value) + move.value) / (tmp_qty + move.product_qty)
            move.write({
                'value': -tmp_value if not quantity else move.value or -tmp_value,
                'price_unit': price_unit,
            })
        elif qty_to_take_on_candidates > 0:
            last_fifo_price = new_standard_price or move.product_id.standard_price
            negative_stock_value = last_fifo_price * -qty_to_take_on_candidates
            tmp_value += abs(negative_stock_value)
            vals = {
                'remaining_qty': move.remaining_qty + -qty_to_take_on_candidates,
                'remaining_value': move.remaining_value + negative_stock_value,
                'value': -tmp_value,
                'price_unit': -1 * last_fifo_price,
            }
            move.write(vals)
        return tmp_value